package com.sunday.common.sharding.sphere.jdbc.leaf.algorithm.sharding.interval;

import com.google.common.base.Preconditions;
import com.google.common.collect.Range;
import com.sunday.common.core.utils.DateTimeUtils;
import lombok.extern.slf4j.Slf4j;

import javax.annotation.concurrent.ThreadSafe;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.Collection;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * 时间线
 *
 * @author zsy
 * @since 2023/3/30
 */
@Slf4j
@ThreadSafe
public class IntervalTimeline {

    // 分片后缀格式
    private final DateTimeFormatter suffixFormatter;
    // 步长单位
    private final ChronoUnit stepUnit;

    // 时间分片下界值
    private final Bound lower;
    // 时间分片上界值
    private final Bound upper;

    // 判定偏移指针
    private volatile String offsetPointer;
    // 检测分区段时间范围
    private volatile Range<LocalDateTime> effectiveInterval;

    public IntervalTimeline(DateTimeFormatter suffixFormatter, ChronoUnit stepUnit, Bound lower, Bound upper) {
        this.suffixFormatter = suffixFormatter;
        this.stepUnit = stepUnit;
        this.lower = lower;
        this.upper = upper;
        initInterval();
    }

    /**
     * 初始化间隔参数
     */
    private void initInterval() {
        LocalDateTime now = LocalDateTime.now();
        this.offsetPointer = now.format(suffixFormatter);
        this.effectiveInterval = Range.closedOpen(DateTimeUtils.floorUnit(lower.minus(now), stepUnit), DateTimeUtils.floorUnit(upper.plus(now), stepUnit));
    }

    /**
     * 偏移计算
     */
    public void offset() {
        while (!this.offsetPointer.equals(LocalDateTime.now().format(suffixFormatter))) {
            synchronized (effectiveInterval) {
                if (!this.offsetPointer.equals(LocalDateTime.now().format(suffixFormatter))) {
                    initInterval();
                }
            }
        }
    }

    public boolean hasContains(LocalDateTime dateTime) {
        offset();
        return effectiveInterval.contains(dateTime);
    }

    public boolean hasIntersection(Range<LocalDateTime> dateTimeRange) {
        offset();
        return effectiveInterval.isConnected(dateTimeRange) && !effectiveInterval.intersection(dateTimeRange).isEmpty();
    }

    public String sharding(final LocalDateTime shardingValue) {
        Preconditions.checkArgument(hasContains(shardingValue), "Sharding value: %s , out of bounds: %s", shardingValue, effectiveInterval);
        return shardingValue.format(suffixFormatter);
    }

    public Collection<String> sharding(final Range<LocalDateTime> shardingValue) {
        Preconditions.checkArgument(hasIntersection(shardingValue), "Sharding value: %s , out of bounds: %s", shardingValue, effectiveInterval);
        // 按照stepUnit计算间隔值
        return Optional.of(DateTimeUtils.gapUnit(shardingValue.lowerEndpoint(), shardingValue.upperEndpoint(), stepUnit))
                .filter(gap -> gap > 0)
                .map(gap -> Stream.iterate(shardingValue.lowerEndpoint(), time -> time.plus(1, stepUnit))
                        .limit(gap + 1)
                        .map(time -> time.format(suffixFormatter))
                        .collect(Collectors.toSet()))
                .orElseGet(() -> Stream.of(shardingValue.lowerEndpoint().format(suffixFormatter), shardingValue.upperEndpoint().format(suffixFormatter))
                        .collect(Collectors.toSet()));
    }

}
